#region AppServices License

// Product: 
// AppServices
// http://appservices.codeplex.com
//
// Author:
// Pawel Idzikowski
// http://www.polenter.com
// mailto:idzikowski@polenter.com?subject=AppServices-Question
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the author(s) be held liable for any damages arising from
// the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
//    1. The origin of this software must not be misrepresented; you must not
//      claim that you wrote the original software. If you use this software
//      in a product, an acknowledgment in the product documentation would be
//      appreciated but is not required.
//
//    2. Altered source versions must be plainly marked as such, and must not
//      be misrepresented as being the original software.
//
//    3. This notice may not be removed or altered from any source distribution.

#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Polenter.Services.Internal;

namespace Polenter.Services.Advanced
{
    /// <summary>
    /// Contains instance of the service and typ the service was declared
    /// </summary>
    public class ServiceInfo
    {
        ///<summary>
        ///</summary>
        ///<param name="serviceType">As what type was the service declared</param>
        ///<param name="service">service instance</param>
        ///<exception cref="ArgumentNullException"></exception>
        public ServiceInfo(Type serviceType, object service)
        {
            if (serviceType == null) throw new ArgumentNullException("serviceType");
            if (service == null) throw new ArgumentNullException("service");
            ServiceType = serviceType;
            Service = service;
        }

        ///<summary>
        /// As what type was the service declared
        ///</summary>
        public Type ServiceType { get; private set; }
        ///<summary>
        /// service instance
        ///</summary>
        public object Service { get; private set; }
    }

    ///<summary>
    ///</summary>
    public class ServiceInfoCollection : KeyedCollection<Type, ServiceInfo>
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        protected override Type GetKeyForItem(ServiceInfo item)
        {
            return item.ServiceType;
        }
    }

    /// <summary>
    /// Sorts services according to their dependency from each other
    /// </summary>
    public class ServiceInfoComparer : IComparer<ServiceInfo>
    {
        private readonly InjectedTypeProvider _injectedTypeProvider = new InjectedTypeProvider();

        #region IComparer<ServiceInfo> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">if there is circular reference between the two items</exception>
        public int Compare(ServiceInfo x, ServiceInfo y)
        {
            if (x.ServiceType == y.ServiceType) return 0;

            // which types are needed by each service
            InjectedTypeInfo xTypeInfo = _injectedTypeProvider.GetInfo(x.Service.GetType());
            InjectedTypeInfo yTypeInfo = _injectedTypeProvider.GetInfo(y.Service.GetType());

            // estimating the service dependency
            bool xNeedsY = Array.IndexOf(xTypeInfo.InjectedTypes, y.ServiceType)>-1;
            bool yNeedsX = Array.IndexOf(yTypeInfo.InjectedTypes, x.ServiceType)>-1;

            if (xNeedsY && yNeedsX)
            {
                // Error
                throw new InvalidOperationException(
                    string.Format(
                        "Circular reference between {0} and {1}. Please fix the references or make an additional mediator class.",
                        xTypeInfo.Type, yTypeInfo.Type));
            }

            if (xNeedsY) return 1;
            if (yNeedsX) return -1;
            return 0;
        }

        #endregion
    }
}